package com.walter.redis.tools;

import com.walter.redis.tools.annotation.RedisLock;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.StandardReflectionParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.Objects;
import java.util.UUID;

/**
 * 分布式锁的AOP实现
 * @author Walter
 * @date 2025/1/11 下午3:13
 **/
@Slf4j
@Aspect
@Component
public class RedisLockAspect {

    private final StandardReflectionParameterNameDiscoverer discoverer = new StandardReflectionParameterNameDiscoverer();
    private final ExpressionParser parser = new SpelExpressionParser();

    @Resource
    private DistributedLockService distributedLockService;

    /**
     * 分布式锁
     * @param pjp
     * @param redisLock
     * @return
     * @throws Throwable
     */
    @Around("@annotation(redisLock)")
    public Object profile(ProceedingJoinPoint pjp, RedisLock redisLock) throws Throwable {
        String requestId = UUID.randomUUID().toString();
        //目标方法
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        //分布式锁的key前缀
        String prefixKey = redisLock.prefix();
        //如果没有声明prefixKey，则默认是类名+方法名+方法参数类型
        if(!StringUtils.hasLength(prefixKey)){
            prefixKey = getDefaultPrefixKey(pjp);
        }
        //获取动态参数的值的EL表达式
        String suffixKeyExp = redisLock.suffix();
        String suffixKey = "";
        if(StringUtils.hasLength(suffixKeyExp)){
            //绑定参数
            EvaluationContext evaluationContext = bindParam(method, pjp.getArgs());
            final Expression expression = parser.parseExpression(suffixKeyExp);
            suffixKey = expression.getValue(evaluationContext,String.class);
        }

        //最终的分布式锁的key：key前缀+后缀组成
        String lockKey = prefixKey + suffixKey;
        log.info("distributedLockKey:{}",lockKey);
        try {
            boolean isLookSuccess = distributedLockService.tryLock(lockKey,requestId, redisLock.lockTime());
            if(isLookSuccess){
                return pjp.proceed();
            }else {
                throw new Exception( "获取分布式锁失败");
            }
        }  catch (RuntimeException e){
            throw e;
        }catch (Exception e){
            log.error("获取分布式锁异常",e);
            throw new Exception("获取分布式锁异常");
        } finally{
            //释放锁
            distributedLockService.releaseLock(lockKey,requestId);
        }
    }

    /**
     *  返回默认的分布式锁前缀：类名+方法名+方法参数类型
     * @param pjp
     * @return
     */
    private String getDefaultPrefixKey(ProceedingJoinPoint pjp){
        //目标类名
        String className = pjp.getTarget().getClass().getName();
        //目标方法
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        //目标方法参数类型
        Class[] parameterTypes = method.getParameterTypes();
        StringBuffer parameterTypeStr = new StringBuffer();
        if(null != parameterTypes && parameterTypes.length > 0){
            parameterTypeStr.append("(");
            for(Class c:parameterTypes){
                parameterTypeStr.append(c.getSimpleName());
            }
            parameterTypeStr.append(")");
        }
        return className+"."+method.getName()+parameterTypeStr.toString();
    }
    /**
     * 将方法的参数名和参数值绑定
     *
     * @param method 方法，根据方法获取参数名
     * @param args   方法的参数值
     * @return
     */
    private EvaluationContext bindParam(Method method, Object[] args) {
        //获取方法的参数名
        String[] params = discoverer.getParameterNames(method);
        //将参数名与参数值对应起来
        EvaluationContext context = new StandardEvaluationContext();
        if (Objects.isNull(params) || params.length == 0) {
            return context;
        }
        for (int len = 0; len < params.length; len++) {
            context.setVariable(params[len], args[len]);
        }
        return context;
    }

}
